//
// Created by LiuYou on 2021/6/18.
//

#include <cstdio>
#include <cstdlib>
#include <cerrno>
#include <unistd.h>
#include <signal.h>
#include <sys/time.h>
#include "AnyTimer.hpp"


enum {
    STATE_RUNNING = 1,
    STATE_CANCELED,
    STATE_OVER

};


struct at_job_st {
    int job_state;
    int sec;
    int time_remain;
    int repeat;
    at_jobfunc_t* jobp;
    void* arg;
};


static struct at_job_st* job[JOB_MAX];
static int inited = 0;
static struct sigaction sigActionSave;


static int get_free_pos() {
    for (int i = 0; i < JOB_MAX; ++i) {
        if (job[i] == nullptr) {
            return i;
        }
    }
    return -1;
}


static void alrm_action(int s, siginfo_t* infop, void* unused) {
    if (infop->si_code != SI_KERNEL) {
        return;
    }
    for (int i = 0; i < JOB_MAX; ++i) {
        if (job[i] != nullptr && job[i]->job_state == STATE_RUNNING) {
            --job[i]->time_remain;
            if (job[i]->time_remain == 0) {
                // TODO 注意: 这里老师写的我没有看懂。
                job[i]->jobp( job[i]->arg );
                if (job[i]->repeat == 1) {
                    job[i]->time_remain = job[i]->sec;
                } else {
                    job[i]->job_state = STATE_OVER;
                }
            }
        }
    }
}


static void module_unload() {
    struct itimerval iTimerVal;
    iTimerVal.it_interval.tv_sec = 0;
    iTimerVal.it_interval.tv_usec = 0;
    iTimerVal.it_value.tv_sec = 0;
    iTimerVal.it_value.tv_usec = 0;
    setitimer( ITIMER_REAL, &iTimerVal, nullptr );
    if (sigaction( SIGALRM, &sigActionSave, nullptr ) < 0) {
        perror( "sigaction()" );
        exit( 1 );
    }
}


static void module_load() {
    struct sigaction sigAction;

    sigAction.sa_sigaction = alrm_action;
    sigemptyset( &sigAction.sa_mask );
    sigAction.sa_flags = SA_SIGINFO;
    if (sigaction( SIGALRM, &sigAction, &sigActionSave ) < 0) {
        perror( "sigaction()" );
        exit( 1 );
    }

    struct itimerval iTimerVal;
    iTimerVal.it_interval.tv_sec = 1;
    iTimerVal.it_interval.tv_usec = 0;
    iTimerVal.it_value.tv_sec = 1;
    iTimerVal.it_value.tv_usec = 0;
    if (setitimer( ITIMER_REAL, &iTimerVal, nullptr ) < 0) {
        perror( "setitimer()" );
        exit( 1 );
    }
    atexit( module_load );
}


int at_addjob(int sec, at_jobfunc_t* jobp, void* arg) {
    if (sec < 0) {
        return -EINVAL;
    }
    if (!inited) {
        module_load();
        inited = 1;
    }
    int pos = get_free_pos();
    if (pos < 0) {
        return -ENOSPC;
    }

    void* pVoid = malloc( sizeof( at_job_st ));
    struct at_job_st* me = static_cast<at_job_st*>(pVoid);
    if (me == nullptr) {
        return -ENOMEM;
    }

    me->job_state = STATE_RUNNING;
    me->sec = sec;
    me->time_remain = me->sec;
    me->jobp = jobp;
    me->arg = arg;
    me->repeat = 0;

    job[pos] = me;
    return pos;
}

int at_addjob_repeat(int sec, at_jobfunc_t* jobp, void* arg) {
    if (sec < 0) {
        return -EINVAL;
    }
    if (!inited) {
        module_load();
        inited = 1;
    }
    int pos = get_free_pos();
    if (pos < 0) {
        return -ENOSPC;
    }

    void* pVoid = malloc( sizeof( at_job_st ));
    struct at_job_st* me = static_cast<at_job_st*>(pVoid);
    if (me == nullptr) {
        return -ENOMEM;
    }

    me->sec = sec;
    me->time_remain = me->sec;
    me->jobp = jobp;
    me->arg = arg;
    me->repeat = 1;
    job[pos] = me;
    me->job_state = STATE_RUNNING;

    return pos;
}


int at_canceljob(int id) {
    if (id < 0 || id >= JOB_MAX || job[id] == nullptr) {
        return -EINVAL;
    }
    if (job[id]->job_state == STATE_CANCELED) {
        return -ECANCELED;
    }
    if (job[id]->job_state == STATE_OVER) {
        return -EBUSY;
    }
    job[id]->job_state = STATE_CANCELED;

    return 0;
}


int at_waitjob(int id) {
    if (id < 0 || id >= JOB_MAX || job[id] == nullptr) {
        return -EINVAL;
    }

    if (job[id]->repeat == 1) {
        return -EBUSY;
    }

    while (job[id]->job_state == STATE_RUNNING) {
        pause();
    }
    if (job[id]->job_state == STATE_CANCELED || job[id]->job_state == STATE_OVER) {
        free( job[id] );
        job[id] = nullptr;
    }

    return 0;
}
































































